//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include <bulverde.h>
#include <INTCBits.h>
#include <DMACBits.h>
#include "drvpub.h"
#include "dma.h"

#define IRQ_DMAC 25
#define DMA_CHANNELS 32

static CDMA g_dma;
// Types for internal usage
typedef struct {
    long  Used;
    void * Handler;
    void * Device;
} DmaChannel;

DmaChannel g_dmaChannels[DMA_CHANNELS];

const long DmaPriority[] = {
        0, 0, 0, 0, 1, 1, 1, 1,
        2, 2, 2, 2, 3, 3, 3, 3,
        0, 0, 0, 0, 1, 1, 1, 1,
        2, 2, 2, 2, 3, 3, 3, 3
};

// Select a channel for the special priority
static long DmaSelectChannel(long priority)
{
    long i;

    if (priority < 0 || priority >= 4) return -1;

    for (i = 0; i < DMA_CHANNELS; i++) {
        if (DmaPriority[i] == priority && !g_dmaChannels[i].Used) {
            // Find an unsed channel!
            g_dmaChannels[i].Used = 1;
            return i;
        }
    }

    // If there is not any free channel in this priority,
    // then select in lower priority.
    return DmaSelectChannel(++priority);
}

EXTERN_C int Drv_DmaInstall(uint_t priority, void *Device, void * Handler)
{
    int Channel;

    Channel = DmaSelectChannel(priority);
    if (Channel == -1) return -1;
    Drv_Assert(Channel >= 0 && Channel < DMA_CHANNELS);

    g_dmaChannels[Channel].Handler = Handler;
    g_dmaChannels[Channel].Device = Device;
    return Channel;
}

static void DispatchDmaIrq(long Channel)
{
    typedef Boolean (*IsrHandler)(void *, Boolean *);
    if (g_dmaChannels[Channel].Used && g_dmaChannels[Channel].Handler) {
        ((IsrHandler)g_dmaChannels[Channel].Handler)(g_dmaChannels[Channel].Device,
                        NULL);
//        DCSR |= DCSR_ENDINTR;
    }
}

// This algorithm will improve the speed of DMA irq dispatch
static void DispatchDmaIrqTerm(long DmaIntTerm, long DmaChannelBase)
{
    switch (DmaIntTerm) {
        case 0x0:
            break;
        case 0x1:
            DispatchDmaIrq(DmaChannelBase + 0);
            break;
        case 0x2:
            DispatchDmaIrq(DmaChannelBase + 1);
            break;
        case 0x3:
            DispatchDmaIrq(DmaChannelBase + 0);
            DispatchDmaIrq(DmaChannelBase + 1);
            break;
        case 0x4:
            DispatchDmaIrq(DmaChannelBase + 2);
            break;
        case 0x5:
            DispatchDmaIrq(DmaChannelBase + 0);
            DispatchDmaIrq(DmaChannelBase + 2);
            break;
        case 0x6:
            DispatchDmaIrq(DmaChannelBase + 1);
            DispatchDmaIrq(DmaChannelBase + 2);
            break;
        case 0x7:
            DispatchDmaIrq(DmaChannelBase + 0);
            DispatchDmaIrq(DmaChannelBase + 1);
            DispatchDmaIrq(DmaChannelBase + 2);
            break;
        case 0x8:
            DispatchDmaIrq(DmaChannelBase + 3);
            break;
        case 0x9:
            DispatchDmaIrq(DmaChannelBase + 0);
            DispatchDmaIrq(DmaChannelBase + 3);
            break;
        case 0xA:
            DispatchDmaIrq(DmaChannelBase + 1);
            DispatchDmaIrq(DmaChannelBase + 3);
            break;
        case 0xB:
            DispatchDmaIrq(DmaChannelBase + 0);
            DispatchDmaIrq(DmaChannelBase + 1);
            DispatchDmaIrq(DmaChannelBase + 3);
            break;
        case 0xC:
            DispatchDmaIrq(DmaChannelBase + 2);
            DispatchDmaIrq(DmaChannelBase + 3);
            break;
        case 0xD:
            DispatchDmaIrq(DmaChannelBase + 0);
            DispatchDmaIrq(DmaChannelBase + 2);
            DispatchDmaIrq(DmaChannelBase + 3);
            break;
        case 0xE:
            DispatchDmaIrq(DmaChannelBase + 1);
            DispatchDmaIrq(DmaChannelBase + 2);
            DispatchDmaIrq(DmaChannelBase + 3);
            break;
        case 0xF:
            DispatchDmaIrq(DmaChannelBase + 0);
            DispatchDmaIrq(DmaChannelBase + 1);
            DispatchDmaIrq(DmaChannelBase + 2);
            DispatchDmaIrq(DmaChannelBase + 3);
            break;
    }
}

// Interrupt routine for DMA
void CDECL DmaIsr(irq_t irq, void * pvDevice, InterruptContext * pContext)
{
    long dint;
    long i;

//    INTC_DMAC_INT_DIS(ICMR);
    dint = DINT;
    for (i = 0; i < DMA_CHANNELS; i += 4) {
        if (dint == 0) break; // No interrupt
        DispatchDmaIrqTerm(dint & 0xF, i);
        dint >>= 4;
    }
//    INTC_DMAC_INT_EN(ICMR);
}

// Add the DMA device to system
Boolean DmaInit()
{
    long i;

    DzRegisterIsr(IRQ_DMAC, IPL10, (isr_t)DmaIsr, NULL);

    for (i = 0; i < DMA_CHANNELS; i++) {
        g_dmaChannels[i].Used = 0;
        g_dmaChannels[i].Handler = NULL;
        g_dmaChannels[i].Device = NULL;
    }

    return TRUE;
}

EXTERN IDeviceDriver * CDECL CreateDMA(UInt32 uDeviceNo, void *pvParameter)
{
    if(!DmaInit()) return NULL;
    cprintf("Add public driver library successfully!\n");
    return &g_dma;
}

ECode CDMA::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf &buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CDMA::Read(
                /* [in] */ Int64 u64Offset,
                /* [in] */ Int32 bytesToRead,
                /* [out] */ MemoryBuf * pBuffer,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CDMA::Control(
                /* [in] */ Handle32 nControlCode,
                /* [in] */ const MemoryBuf &inBuffer,
                /* [out] */ MemoryBuf * pOutBuffer,
                /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}
